package pl.szpadel.android.gadu;

import java.util.ArrayList;
import java.util.Collection;
import java.util.Date;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;

import android.content.Context;

import pl.szpadel.android.gadu.packets.BasePacket;
import pl.szpadel.android.gadu.packets.ReceivedPacket;
import pl.szpadel.android.gadu.packets.RecvMsg80;
import pl.szpadel.android.gadu.packets.SendMsg80;
import pl.szpadel.android.gadu.packets.SendMsgAck;

/// Handles incoming messages, manages chats, message notifications and chat history
public class ChatManager implements IAppComponent {

	static private final String TAG = "ChatManager";
	
	//////////////////////////////////////
	// chat and message types
	public class Message {
		public long sender;
		public String text;
		public Date time;
	}
	
	public class Chat {
		private long mRemoteUser;
		private Date mLastActivityTime;
		private ArrayList<Message> mMessages = new ArrayList<Message>();
		/// change observers
		HashSet<ChatListAdapter> mObservers = new HashSet<ChatListAdapter>();
		
		/// C-tor
		Chat(long remoteUser) {
			mRemoteUser = remoteUser;
			mLastActivityTime = new Date();
		}
		
		/// Adds message
		public void addMessage(Message msg) {
			mMessages.add(msg);
			mLastActivityTime = new Date();
			
			// notify observers
			for(ChatListAdapter o : mObservers) {
				o.onChanged();
			}
		}
		
		/// Returns messages
		public ArrayList<Message> getMessages() {
			return mMessages;
		}
		
		/// Returns last message
		public Message getLastMessage() {
			return mMessages.get(mMessages.size() - 1);
		}
		
		/// Returns remote user id
		public long getRemoteUser() {
			return mRemoteUser;
		}
		
		/// Adds observer
		public void registerObserver(ChatListAdapter o) {
			mObservers.add(o);
		}
		
		/// Removes observer
		public void unregisterObserver(ChatListAdapter o) {
			if (mObservers.contains(o)){
				mObservers.remove(o);
			}
		}
	}; // Chat class
	
	/// Chats
	private HashMap<Long, Chat> mChats = new HashMap<Long, Chat>();
	
	/// Set of uins with unread messages
	private HashSet<Long> mUnread = new HashSet<Long>();
	
	// DB helper
	MessageDatabaseHelper mDBHelper;
	
	//////////////////////////////////////
	// interface
	
	// C-tor
	public ChatManager(Context ctx) {
		mDBHelper = new MessageDatabaseHelper(ctx);
	}
	
	@Override
	public void onPacket(ReceivedPacket packet) {
		switch(packet.getType()) {
		case BasePacket.TYPE_RECV_MSG80: {
			RecvMsg80 msg = (RecvMsg80)packet;
			onMessage(msg);
			break;
		}
		case BasePacket.TYPE_SEND_MSG_ACK: {
			SendMsgAck msg = (SendMsgAck)packet;
			onAck(msg);
		}
		}
	}

	@Override
	public void onConnectionStateChanged(ConnectionState state) {
		// nothing
	}
	
	/// Gets currently open chats
	public Map<Long, Chat> getChats() {
		return mChats;
	}
	
	/// Returns chat for specific user. Creates if doesn't exists
	public Chat getOrCreateChat(long uin) {
		// do we have the chat?
		if (mChats.containsKey(uin)){
			return mChats.get(uin);
		} else {
			Chat chat = new Chat(uin);
			mChats.put(uin, chat);
			loadArchivalMessages(chat);
			notifyChatAdded(chat);
			
			return chat;
		}
	}
	
	/// Info from UI that chat was displayed and all notification for this chat can be cleared
	public void chatDisplayed(long uin) {
		AGLog.d(TAG, "Chat displayed for " + uin);
		// remove from unread
		mUnread.remove(uin);
		
		if (mUnread.isEmpty()) {
			// hide all notifications
			AGLog.d(TAG, "Hiding notification");
			App.getInstance().getUserNotification().hideNotification();
		} else {
			// update notification
			long sender = mUnread.iterator().next();
			Chat chat = mChats.get(sender);
			Message msg = chat.getLastMessage();
			AGLog.d(TAG, "Displaing notification for remaining unread messages, intent set to " + sender);
			App.getInstance().getUserNotification().notifyUnread(msg.text, sender, mUnread.size());
		}
		
		// set 'delivered' flag for all incoming messages on this chat
		mDBHelper.setDelivered(uin, App.getInstance().getConfig().getUin());
	}
	
	/// Interface for sending messages. Creates chat for the message if neccesary
	/// and manages chat history
	public void sendMessage(long uin, String text) {
		Chat chat = getOrCreateChat(uin);
		Message msg = new Message();
		msg.sender = App.getInstance().getConfig().getUin();
		msg.text = text;
		msg.time = new Date();
		
		chat.addMessage(msg);
		
		// get current timestamp, it will be uses as seq number
		int timestamp = (int) (msg.time.getTime() / 1000);
		
		// and now actually send
		SendMsg80 packet = new SendMsg80(uin, text, timestamp);
		App.getInstance().getCommunicationService().sendPacket(packet);
		
		// store into DB
		mDBHelper.addMessage(msg.sender, uin, timestamp, false, text);
	}

	//////////////////////////////////////
	// implementation
	
	private void onMessage(RecvMsg80 packet){
		long sender = packet.sender;
		AGLog.i(TAG, "msg received from " + sender);
		
		// is there a existing chat?
		Chat chat;
		if (!mChats.containsKey(sender)) {
			AGLog.d(TAG, "no chat for the message, creating");
			chat = new Chat(sender);
			mChats.put(sender, chat);
			notifyChatAdded(chat);
		} else {
			AGLog.d(TAG, "chat exists");
			chat = mChats.get(sender);
		}
		
		// construct message
		Message msg = new Message();
		msg.sender = sender;
		msg.text = packet.plainMessage;
		msg.time = new Date();
		
		// add it to the chat
		chat.addMessage(msg);
		
		boolean delivered = notifyOfNewMessage(msg);
		
		// store into DB
		int timestamp = (int) (msg.time.getTime() / 1000);
		mDBHelper.addMessage(msg.sender, App.getInstance().getConfig().getUin(), timestamp, delivered, msg.text);
	}

	/// Notifies chat activity that new chat has been created
	private void notifyChatAdded(Chat chat) {
		ChatActivity a = App.getInstance().getChatActivity();
		if (a != null) {
			a.onChatCreated(chat);
		}
	}
	
	/// If the message is not immediately displayed in the view, adds uin to unread messages and updates system notification
	/// returns true if messages was immediately displayed, false if notification was used
	private boolean notifyOfNewMessage(Message msg) {
		ChatActivity a = App.getInstance().getChatActivity();
		if (a != null) {
			if (a.onNewMessage(msg)){
				AGLog.d(TAG, "Activity sais that chat for " + msg.sender + " is displayed and not niotification is needed");
				return true;
			}
		}
		
		AGLog.d(TAG, "Displaing notification for message from " + msg.sender);
		// notify loudly only when first unread message incomes from user
		if (!mUnread.contains(msg.sender)) {
			mUnread.add(msg.sender);
			App.getInstance().getUserNotification().notifyNewMessage(msg.text , msg.sender, mUnread.size());
		} else {
			// otherwise silent text ticker will be enough
			App.getInstance().getUserNotification().notifyUnread(msg.text , msg.sender, mUnread.size());
		}
		
		return false;
	}

	@Override
	public void onStatusChanged(Status status) {
		// nothing
		
	}

	@Override
	public void onConfigChanged() {
		// nothing
	}
	
	/// handles msh ack packet
	private void onAck(SendMsgAck ack) {
		// set delivered flag on the message
		mDBHelper.setDelivered(App.getInstance().getConfig().getUin(), ack.recipient, ack.seq);
	}

	/// Loads archival messages from DB to the chat
	private void loadArchivalMessages(Chat chat) {
		Collection<MessageDatabaseHelper.Message> messages = mDBHelper.getMessages(chat.getRemoteUser(), App.getInstance().getConfig().getUin(), 50);
		for (MessageDatabaseHelper.Message msg : messages) {
			// translate between message types (hint: use unified global message type!)
			Message chatMessage = new Message();
			chatMessage.sender = msg.sender;
			chatMessage.text = msg.text;
			chatMessage.time = new Date((long)(msg.timestamp) * 1000);
			chat.addMessage(chatMessage);
		}
	}
	
}
